#!/usr/bin/env bash

if ${__test_sh__:-false}; then
    return
else
    __test_sh__=true
fi

declare -a tests
declare command_output
declare expect_total expect_success
declare errors
declare snapshot
declare start_test_time

function test::is_manual_test()
{
    [[ "$test" =~ (::manual$) ]]
}

function test::register_test()
{
    tests+=($*)
}

function test::run_cmd()
{
    command_output="$("$@")"
}

function test::run_snapshot()
{
    snapshot="$*"
    snapshot="${snapshot//:/_}"
    snapshot="snapshot/${snapshot// /_}"
    if [ ! -e "$snapshot" ]; then
        echo "generate snapshot at $snapshot"
        $* | xxd > $snapshot
    else
        test::run_cmd $*
        result=$(xxd -r $snapshot)
        test::should_be "$result"
    fi
}

function test::should_be()
{
    ((expect_total++))
    local left_md5sum="$(md5sum <<< "$command_output")"
    local right_md5sum="$(md5sum <<< "$1")"
    if [ "$left_md5sum" == "$right_md5sum" ]; then
        printf "$(cctl g).$(cctl 0)"
        ((expect_success++))
    else
        printf "$(cctl r)x$(cctl 0)"
        errors+=("test::should_be ('$command_output' != '$1')")
        if (( ${#command_output} >= 32 )); then
            errors+=("('$left_md5sum' != '$right_md5sum')")
            xxd <<< "$command_output" > /tmp/1
            xxd <<< "$1" > /tmp/2
            errors+=("diff: $(icdiff /tmp/1 /tmp/2)")
        fi
    fi
}

function test::succed()
{
    ((expect_total++))
    ((expect_success++))
}

function test::failed()
{
    ((expect_total++))
    errors+=("failed $*")
}

function test::should_in()
{
    ((expect_total++))
    if (( $1 <= $command_output && $command_output <= $2 )); then
        printf "$(cctl g).$(cctl 0)"
        ((expect_success++))
    else
        printf "$(cctl r)x$(cctl 0)"
        errors+=("test::should_in ('$command_output' not in [$1, $2])")
    fi
}

function test::must_be()
{
    if [ "$command_output" != "$1" ]; then
        echo "$(cctl r)test::must_be ('$command_output' != '$1')$(cctl 0)"
        exit 1
    fi
}

function test::should_return()
{
    local retval="$1"
    shift
    ((expect_total++))
    slient "$@"
    local rv=$?
    if [ "$retval" == "-1" ] && [ "$rv" != "0" ]; then
        rv="-1"
    fi
    if [ "$rv" == "$retval" ]; then
        printf "$(cctl g).$(cctl 0)"
        ((expect_success++))
    else
        errors+=("test::should_return $retval ('$*' return $rv)")
        printf "$(cctl r)x$(cctl 0)"
    fi
}

function test::should_return_true()
{
    test::should_return 0 $*
}

function test::should_return_false()
{
    test::should_return -1 $*
}

function test::var_should_be()
{
    ((expect_total++))
    if [ "${!1}" == "$2" ]; then
        printf "$(cctl g).$(cctl 0)"
        ((expect_success++))
    else
        printf "$(cctl r)x$(cctl 0)"
        errors+=("test::var_should_be ('\$$1'{${!1}} != '$2')")
    fi
}

include "arguments.sh"
include "output.sh"

function test::run_tests()
{
    function test::run_tests::before1()
    {
        if test::is_manual_test; then
            echo "    running test $(cctl c)$test$(cctl 0)"
        else
            echo "    running test $(cctl c)$test$(cctl 0) {"
            printf "        "
        fi

        ((test_total++))
        expect_total=0
        expect_success=0
        errors=()
        start_test_time="$(date +%s%N)"
    }

    function test::run_tests::before2()
    {
        $debug && set -x
    }

    function test::run_tests::after2()
    {
        set +x
    }

    function test::run_tests::after1()
    {
        if ! test::is_manual_test; then
            printf "\n"
            local i
            for (( i = 0; i < ${#errors[*]}; i++ )); do
                echo -e "        $(cctl y)${errors[$i]}$(cctl 0)"
            done
            printf "    } "
        fi

        if [ "$expect_total" = "$expect_success" ]; then
            ((test_success++))
            printf "$(cctl g)"
        else
            printf "$(cctl r)"
        fi
        echo "($expect_success/$expect_total) $((($(date +%s%N) - $start_test_time) / 1000 / 1000))ms$(cctl 0)"
    }

    function test::run_tests::run_test()
    {
        test::run_tests::before1
        test::run_tests::before2
        $1
        test::run_tests::after2
        test::run_tests::after1
    }

    function test::run_tests::run()
    {
        for test in ${tests[*]}; do
            if test::is_manual_test; then
                if $manual; then
                    if (( ${#allows[*]} > 0 )); then
                        local can=false
                        for allow in ${allows[*]}; do
                            if [[ "$test" =~ $allow ]]; then
                                can=true
                                break
                            fi
                        done

                        if ! $can; then
                            echo "    skip test $(cctl c)$test$(cctl 0)"
                            continue
                        fi
                    fi

                    test::run_tests::run_test $test
                    continue
                else
                    echo "    skip manual test $(cctl c)$test$(cctl 0)"
                fi
            else
                if $manual; then
                    echo "    not manual test $(cctl c)$test$(cctl 0)"
                fi
            fi

            if ! test::is_manual_test && ! $manual; then
                if (( ${#allows[*]} > 0 )); then
                    local can=false
                    for allow in ${allows[*]}; do
                        if [[ "$test" =~ $allow ]]; then
                            can=true
                            break
                        fi
                    done

                    if ! $can; then
                        echo "    skip test $(cctl c)$test$(cctl 0)"
                        continue
                    fi
                fi

                test::run_tests::run_test $test
                continue
            fi
        done
    }

    local debug=false manual=false
    local allows=()
    local test_total=0
    local test_success=0

    arguments $*
    while ! arguments::is_end; do
        if arguments::is_option; then
            case "$(arguments::get_option)" in
                manual)
                    manual=true
                    ;;
                debug)
                    debug=true
                    ;;
                debuglog)
                    debuglog=true
                    ;;
                *)
                    echo "not support $(arguments::get)"
                    ;;
            esac
            arguments::next
        else
            allows+=($(arguments::get))
            arguments::next
        fi
    done
    arguments::destroy

    function assert_fail()
    {
        log_depth_inc
        log_depth_inc
        log_depth_inc
        loge "assert_fail: $1" 2>/dev/null
        log_depth_dec
        log_depth_dec
        log_depth_dec
        return 1
    }

    echo "running $((${#manual_test_maps[*]} + ${#tests[*]})) tests"
    echo "{"

    test::run_tests::run

    echo "}"
    echo "running tests finish, ($test_success/$test_total)"
}

function test::get_temp_path()
{
    mktemp --dry-run "/tmp/${1-test}.XXXXXXXX"
}
